/*
 * Copyright (C) 2015 Niek Linnenbank
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <FreeNOS/Config.h>
#include <FreeNOS/Support.h>
#include <FreeNOS/System.h>
#include <Macros.h>
#include <MemoryBlock.h>
#include <arm/ARMControl.h>
#include <arm/ARMPaging.h>
#include <arm/broadcom/BroadcomInterrupt.h>
#include <arm/broadcom/BroadcomTimer.h>
#include <PL011.h>
#include <DeviceLog.h>
#include "RaspberryKernel.h"

extern Address __start, __end, __bootimg;

static u32 ALIGN(16 * 1024) SECTION(".data") tmpPageDir[4096];

extern C int kernel_main(void)
{
    // Invalidate all caches now
    Arch::Cache cache;
    cache.invalidate(Cache::Unified); // 数据cache和指令cache清理

#ifdef ARMV7
    // Raise the SMP bit for ARMv7
    ARMControl ctrl;
    ctrl.set(ARMControl::SMPBit); // 其他核启动
#endif

    // Fill coreInfo
    BootImage *bootimage = (BootImage *)&__bootimg;   // bootimg段从链接脚本中定义得到的
    MemoryBlock::set(&coreInfo, 0, sizeof(CoreInfo)); // coreInfo也是链接脚本中定义的一个数据段
    coreInfo.bootImageAddress = (Address)(bootimage); // 内核image文件的存放地址，一般是在rom上
    coreInfo.bootImageSize = bootimage->bootImageSize;
    coreInfo.kernel.phys = (Address)&__start;         // __start定义在链接脚本，一般应该是soc的内存物理起始地址，或者再加个偏移量。内核image会从rom里被拷贝到这个内存的物理地址，然后从这里开始运行，理解成内核文件在ram里的镜像
    coreInfo.kernel.size = ((Address)&__end - (Address)&__start); // 内核镜像的运行时的大小，包括代码段，数据段，bss段等
    /* 可以看出，和链接脚本有关的部分，核心目标都是如何把一个程序文件(这里是指os内核文件)写入rom里，以及当想要运行这个程序文件时，怎么把它从rom里读出来，
        然后分配一定范围的内存，使这个程序文件的代码，数据，bss段，可以被程序自身的代码，正确的寻址到。
        此时，程序文件的数据，bss，init等段，应该理解成这个程序文件的全局变量段，自定义变量段，未初始化变量段。
        注意，这些段目前都是物理地址。后续会把这些段(即整个kernel)映射到虚拟地址上，以便应用态进程可以访问到内核进程的资源。(kernel可以理解成一个大进程)
        栈的物理地址范围，还没有看到。*/
    
    //RAM_ADDR在程序里定义成0了，RAM_SIZE是896MB，这里的ram应该理解成堆内存，且是被os完全预定下的堆，
    //理解成定义一个很大的数组，占据了这段内存空间，然后这段内存是完全由os管理的。即一个程序自己管理的内存，此时管理程序是os。
    //os针对堆内存的管理有很多方法，比如buddy，分页，虚拟存储等等。
    //注意这个要和后面的堆区分开，后面的堆是内核程序为了运行自身代码，而使用的堆，是我们最广泛理解的堆内存，new，malloc由此分配堆内存。
    coreInfo.memory.phys = RAM_ADDR;
    coreInfo.memory.size = RAM_SIZE;

    // Prepare early page tables
    //划分了地址段，并定义了os内核物理地址到虚拟地址的转换关系，同时定义了用户态虚拟地址的开始范围，但没有定义其物理地址，在用户态程序运行时，会动态映射
    Arch::MemoryMap mem;
    //传入地址映射关系，和顶级页表，以及起始内存地址，完成映射，这块内容比较大，暂时不展开，待后续具体分析内存管理。
    ARMPaging paging(&mem, (Address)&tmpPageDir, RAM_ADDR);

    // Activate MMU
    //完成各段的页映射，包括内核程序的重映射，内核栈，io段的重映射，待深入展开
    paging.initialize();
    //使能mmu，初始化cache，页表寄存器，清空TLB等，特定cpu的汇编代码
    paging.activate(true);
    //截至现在，内核的内存管理方面已经全部完成了。

    //缩短bss段的长度?
    // Clear BSS
    clearBSS();

    //初始化内核堆，并设置默认的堆内存分配器是pool
    // Initialize heap
    Kernel::initializeHeap();

    // Run all constructors first
    //这里的段定义在链接脚本中，类似linux的initcall，我们的代码可以指定到该段里，然后就会在这里被逐个调用
    constructors();

    // Open the serial console as default Log
    PL011 pl011(UART0_IRQ);
    pl011.initialize();

    DeviceLog console(pl011);
    console.setMinimumLogLevel(Log::Notice);

    // Create the kernel
    //注册中断，注册内核定时器，设置cpu状态等，待深入
    RaspberryKernel kernel(&coreInfo);

    // Run the kernel
    return kernel.run();
}
